package edu.northwestern.cbits.purple_robot_manager.probes.builtin;
import java.util.ArrayList;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import edu.northwestern.cbits.purple_robot_manager.EncryptionManager;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.probes.Probe;
public class BluetoothDevicesProbe extends Probe
{
public static final String PROBE_NAME = "edu.northwestern.cbits.purple_robot_manager.probes.builtin.BluetoothDevicesProbe";
protected static final String NAME = "BLUETOOTH_NAME";
protected static final String ADDRESS = "BLUETOOTH_ADDRESS";
protected static final String MAJOR_CLASS = "DEVICE MAJOR CLASS";
protected static final String MINOR_CLASS = "DEVICE MINOR CLASS";
protected static final String BOND_STATE = "BOND_STATE";
protected static final String DEVICES_COUNT = "DEVICE_COUNT";
protected static final String DEVICES = "DEVICES";
private static final boolean DEFAULT_ENABLED = false;
private static final String ENABLED = "config_probe_bluetooth_enabled";
private static final String HASH_DATA = "config_probe_bluetooth_hash_data";
private static final String FREQUENCY = "config_probe_bluetooth_frequency";
private long _lastCheck = 0;
private BroadcastReceiver _receiver = null;
private BluetoothAdapter _adapter = null;
private final ArrayList<Bundle> _foundDevices = new ArrayList<>();
@Override
public String getPreferenceKey() {
return "built_in_bluetooth";
}
@Override
public String name(Context context)
{
return BluetoothDevicesProbe.PROBE_NAME;
}
@Override
public String title(Context context)
{
return context.getString(R.string.title_bluetooth_probe);
}
@Override
public String probeCategory(Context context)
{
return context.getResources().getString(R.string.probe_other_devices_category);
}
public static String majorDeviceClass(int majorClass)
{
String deviceClassName = "Unknown";
switch (majorClass)
{
case BluetoothClass.Device.Major.AUDIO_VIDEO:
deviceClassName = "Audio/Video";
break;
case BluetoothClass.Device.Major.COMPUTER:
deviceClassName = "Computer";
break;
case BluetoothClass.Device.Major.HEALTH:
deviceClassName = "Health";
break;
case BluetoothClass.Device.Major.IMAGING:
deviceClassName = "Imaging";
break;
case BluetoothClass.Device.Major.MISC:
deviceClassName = "Miscellaneous";
break;
case BluetoothClass.Device.Major.NETWORKING:
deviceClassName = "Networking";
break;
case BluetoothClass.Device.Major.PERIPHERAL:
deviceClassName = "Peripheral";
break;
case BluetoothClass.Device.Major.PHONE:
deviceClassName = "Phone";
break;
case BluetoothClass.Device.Major.TOY:
deviceClassName = "Toy";
break;
case BluetoothClass.Device.Major.UNCATEGORIZED:
deviceClassName = "Uncategorized";
break;
case BluetoothClass.Device.Major.WEARABLE:
deviceClassName = "Wearable";
break;
}
return String.format("0x%08x %s", majorClass, deviceClassName);
}
public static String minorDeviceClass(int minorClass)
{
String deviceClassName = "Unknown";
switch (minorClass)
{
case BluetoothClass.Device.AUDIO_VIDEO_CAMCORDER:
deviceClassName = "Camcorder";
break;
case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
deviceClassName = "Car Audio";
break;
case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
deviceClassName = "Handsfree";
break;
case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
deviceClassName = "Headphones";
break;
case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
deviceClassName = "HiFi Audio";
break;
case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
deviceClassName = "Loudspeaker";
break;
case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE:
deviceClassName = "Microphone";
break;
case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
deviceClassName = "Portable Audio";
break;
case BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX:
deviceClassName = "Set-Top Box";
break;
case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED:
deviceClassName = "Uncategorized";
break;
case BluetoothClass.Device.AUDIO_VIDEO_VCR:
deviceClassName = "VCR";
break;
case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CAMERA:
deviceClassName = "Video Camera";
break;
case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING:
deviceClassName = "Video Conferencing";
break;
case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER:
deviceClassName = "Display & Loudspeaker";
break;
case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_GAMING_TOY:
deviceClassName = "Gaming Toy";
break;
case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_MONITOR:
deviceClassName = "Video Monitor";
break;
case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
deviceClassName = "Wearable Headset";
break;
case BluetoothClass.Device.COMPUTER_DESKTOP:
deviceClassName = "Desktop";
break;
case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA:
deviceClassName = "Handheld PC or PDA";
break;
case BluetoothClass.Device.COMPUTER_LAPTOP:
deviceClassName = "Laptop";
break;
case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA:
deviceClassName = "Palm-Size PC or PDA";
break;
case BluetoothClass.Device.COMPUTER_SERVER:
deviceClassName = "Server";
break;
case BluetoothClass.Device.COMPUTER_UNCATEGORIZED:
deviceClassName = "Uncategorized";
break;
case BluetoothClass.Device.COMPUTER_WEARABLE:
deviceClassName = "Wearable";
break;
case BluetoothClass.Device.HEALTH_BLOOD_PRESSURE:
deviceClassName = "Blood Pressure";
break;
case BluetoothClass.Device.HEALTH_DATA_DISPLAY:
deviceClassName = "Data Display";
break;
case BluetoothClass.Device.HEALTH_GLUCOSE:
deviceClassName = "Glucose";
break;
case BluetoothClass.Device.HEALTH_PULSE_OXIMETER:
deviceClassName = "Oximeter";
break;
case BluetoothClass.Device.HEALTH_PULSE_RATE:
deviceClassName = "Pulse Rate";
break;
case BluetoothClass.Device.HEALTH_THERMOMETER:
deviceClassName = "Thermometer";
break;
case BluetoothClass.Device.HEALTH_UNCATEGORIZED:
deviceClassName = "Uncategorized";
break;
case BluetoothClass.Device.HEALTH_WEIGHING:
deviceClassName = "Weighing";
break;
case BluetoothClass.Device.PHONE_CELLULAR:
deviceClassName = "Cellular";
break;
case BluetoothClass.Device.PHONE_CORDLESS:
deviceClassName = "Cordless";
break;
case BluetoothClass.Device.PHONE_ISDN:
deviceClassName = "ISDN";
break;
case BluetoothClass.Device.PHONE_MODEM_OR_GATEWAY:
deviceClassName = "Modem or Gateway";
break;
case BluetoothClass.Device.PHONE_SMART:
deviceClassName = "Smartphone";
break;
case BluetoothClass.Device.PHONE_UNCATEGORIZED:
deviceClassName = "Uncategorized";
break;
case BluetoothClass.Device.TOY_CONTROLLER:
deviceClassName = "Controller";
break;
case BluetoothClass.Device.TOY_DOLL_ACTION_FIGURE:
deviceClassName = "Doll or Action Figure";
break;
case BluetoothClass.Device.TOY_GAME:
deviceClassName = "Game";
break;
case BluetoothClass.Device.TOY_ROBOT:
deviceClassName = "Robot";
break;
case BluetoothClass.Device.TOY_UNCATEGORIZED:
deviceClassName = "Uncategorized";
break;
case BluetoothClass.Device.TOY_VEHICLE:
deviceClassName = "Vehicle";
break;
case BluetoothClass.Device.WEARABLE_GLASSES:
deviceClassName = "Glasses";
break;
case BluetoothClass.Device.WEARABLE_HELMET:
deviceClassName = "Helmet";
break;
case BluetoothClass.Device.WEARABLE_JACKET:
deviceClassName = "Jacket";
break;
case BluetoothClass.Device.WEARABLE_PAGER:
deviceClassName = "Pager";
break;
case BluetoothClass.Device.WEARABLE_UNCATEGORIZED:
deviceClassName = "Uncategorized";
break;
case BluetoothClass.Device.WEARABLE_WRIST_WATCH:
deviceClassName = "Wrist Watch";
break;
}
return String.format("0x%08x %s", minorClass, deviceClassName);
}
@Override
public void enable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(BluetoothDevicesProbe.ENABLED, true);
e.commit();
}
@Override
public void disable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(BluetoothDevicesProbe.ENABLED, false);
e.commit();
}
@Override
@SuppressLint("InlinedApi")
public boolean isEnabled(Context context)
{
final SharedPreferences prefs = Probe.getPreferences(context);
final EncryptionManager em = EncryptionManager.getInstance();
final BluetoothDevicesProbe me = this;
if (this._receiver == null)
{
this._receiver = new BroadcastReceiver()
{
@Override
@SuppressWarnings("unchecked")
public void onReceive(Context context, Intent intent)
{
try
{
if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction()))
{
boolean doHash = prefs.getBoolean(BluetoothDevicesProbe.HASH_DATA, Probe.DEFAULT_HASH_DATA);
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Bundle deviceBundle = new Bundle();
if (doHash)
{
deviceBundle.putString(BluetoothDevicesProbe.NAME, em.createHash(context, device.getName()));
deviceBundle.putString(BluetoothDevicesProbe.ADDRESS, em.createHash(context, device.getAddress()));
}
else
{
deviceBundle.putString(BluetoothDevicesProbe.NAME, device.getName());
deviceBundle.putString(BluetoothDevicesProbe.ADDRESS, device.getAddress());
}
deviceBundle.putString(BluetoothDevicesProbe.BOND_STATE, BluetoothDevicesProbe.bondState(device.getBondState()));
BluetoothClass deviceClass = device.getBluetoothClass();
deviceBundle.putString(BluetoothDevicesProbe.MAJOR_CLASS, BluetoothDevicesProbe.majorDeviceClass(deviceClass.getMajorDeviceClass()));
deviceBundle.putString(BluetoothDevicesProbe.MINOR_CLASS, BluetoothDevicesProbe.minorDeviceClass(deviceClass.getDeviceClass()));
me._foundDevices.add(deviceBundle);
}
else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction()))
{
Bundle bundle = new Bundle();
bundle.putString("PROBE", me.name(context));
bundle.putLong("TIMESTAMP", System.currentTimeMillis() / 1000);
bundle.putParcelableArrayList(BluetoothDevicesProbe.DEVICES, (ArrayList<Bundle>) me._foundDevices.clone());
bundle.putInt(BluetoothDevicesProbe.DEVICES_COUNT, me._foundDevices.size());
synchronized (me)
{
me.transmitData(context, bundle);
}
}
else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction()))
me._lastCheck = 0;
}
catch (RuntimeException e)
{
LogManager.getInstance(context).logException(e);
}
}
};
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
filter.addAction(BluetoothAdapter.ACTION_REQUEST_ENABLE);
filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
context.registerReceiver(this._receiver, filter);
}
long now = System.currentTimeMillis();
boolean enabled = super.isEnabled(context);
if (enabled)
enabled = prefs.getBoolean(BluetoothDevicesProbe.ENABLED, BluetoothDevicesProbe.DEFAULT_ENABLED);
if (enabled)
{
synchronized (this)
{
try
{
long freq = Long.parseLong(prefs.getString(BluetoothDevicesProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY));
if (now - this._lastCheck > freq)
{
Looper looper = Looper.myLooper();
if (looper == null)
Looper.prepare();
if (this._adapter == null)
this._adapter = BluetoothAdapter.getDefaultAdapter();
if (this._adapter != null && this._adapter.isEnabled())
{
this._foundDevices.clear();
this._adapter.startDiscovery();
}
this._lastCheck = now;
}
}
catch (SecurityException e)
{
LogManager.getInstance(context).logException(e);
}
}
return true;
}
else
{
if (this._adapter != null)
{
this._adapter.cancelDiscovery();
this._adapter = null;
}
}
return false;
}
protected static String bondState(int bondState)
{
switch (bondState)
{
case BluetoothDevice.BOND_BONDED:
return "Paired";
case BluetoothDevice.BOND_BONDING:
return "Pairing";
case BluetoothDevice.BOND_NONE:
return "Not Paired";
}
return "Unknown or Error";
}
@Override
public String summarizeValue(Context context, Bundle bundle)
{
int count = (int) bundle.getDouble(BluetoothDevicesProbe.DEVICES_COUNT);
return String.format(context.getResources().getString(R.string.summary_bluetooth_probe), count);
}
private Bundle bundleForDevicesArray(Context context, ArrayList<Bundle> objects)
{
Bundle bundle = new Bundle();
if (objects == null)
return bundle;
for (Bundle value : objects)
{
ArrayList<String> keys = new ArrayList<>();
String key = String.format(context.getString(R.string.display_bluetooth_device_title), value.getString(BluetoothDevicesProbe.NAME), value.getString(BluetoothDevicesProbe.ADDRESS));
Bundle deviceBundle = new Bundle();
deviceBundle.putString(context.getString(R.string.display_bluetooth_device_title_label), value.getString(BluetoothDevicesProbe.NAME));
deviceBundle.putString(context.getString(R.string.display_bluetooth_device_address_label), value.getString(BluetoothDevicesProbe.ADDRESS));
deviceBundle.putString(context.getString(R.string.display_bluetooth_device_pair), value.getString(BluetoothDevicesProbe.BOND_STATE));
deviceBundle.putString(context.getString(R.string.display_bluetooth_device_major), value.getString(BluetoothDevicesProbe.MAJOR_CLASS));
deviceBundle.putString(context.getString(R.string.display_bluetooth_device_minor), value.getString(BluetoothDevicesProbe.MINOR_CLASS));
keys.add(context.getString(R.string.display_bluetooth_device_title_label));
keys.add(context.getString(R.string.display_bluetooth_device_address_label));
keys.add(context.getString(R.string.display_bluetooth_device_pair));
keys.add(context.getString(R.string.display_bluetooth_device_major));
keys.add(context.getString(R.string.display_bluetooth_device_minor));
deviceBundle.putStringArrayList("KEY_ORDER", keys);
bundle.putBundle(key, deviceBundle);
}
return bundle;
}
@Override
public Bundle formattedBundle(Context context, Bundle bundle)
{
Bundle formatted = super.formattedBundle(context, bundle);
@SuppressWarnings("unchecked")
ArrayList<Bundle> array = (ArrayList<Bundle>) bundle.get(BluetoothDevicesProbe.DEVICES);
int count = (int) bundle.getDouble(BluetoothDevicesProbe.DEVICES_COUNT);
Bundle devicesBundle = this.bundleForDevicesArray(context, array);
formatted.putBundle(String.format(context.getString(R.string.display_bluetooth_devices_title), count), devicesBundle);
return formatted;
}
@Override
public Map<String, Object> configuration(Context context)
{
Map<String, Object> map = super.configuration(context);
SharedPreferences prefs = Probe.getPreferences(context);
long freq = Long.parseLong(prefs.getString(BluetoothDevicesProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY));
map.put(Probe.PROBE_FREQUENCY, freq);
boolean hash = prefs.getBoolean(BluetoothDevicesProbe.HASH_DATA, Probe.DEFAULT_HASH_DATA);
map.put(Probe.HASH_DATA, hash);
return map;
}
@Override
public void updateFromMap(Context context, Map<String, Object> params)
{
super.updateFromMap(context, params);
if (params.containsKey(Probe.PROBE_FREQUENCY))
{
Object frequency = params.get(Probe.PROBE_FREQUENCY);
if (frequency instanceof Double)
{
frequency = ((Double) frequency).longValue();
}
if (frequency instanceof Long)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putString(BluetoothDevicesProbe.FREQUENCY, frequency.toString());
e.commit();
}
}
if (params.containsKey(Probe.HASH_DATA))
{
Object hash = params.get(Probe.HASH_DATA);
if (hash instanceof Boolean)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(BluetoothDevicesProbe.HASH_DATA, (Boolean) hash);
e.commit();
}
}
}
@Override
public String summary(Context context)
{
return context.getString(R.string.summary_bluetooth_probe_desc);
}
@Override
@SuppressWarnings("deprecation")
public PreferenceScreen preferenceScreen(Context context, PreferenceManager manager)
{
PreferenceScreen screen = super.preferenceScreen(context, manager);
screen.setTitle(this.title(context));
screen.setSummary(R.string.summary_bluetooth_probe_desc);
CheckBoxPreference enabled = new CheckBoxPreference(context);
enabled.setTitle(R.string.title_enable_probe);
enabled.setKey(BluetoothDevicesProbe.ENABLED);
enabled.setDefaultValue(BluetoothDevicesProbe.DEFAULT_ENABLED);
screen.addPreference(enabled);
FlexibleListPreference duration = new FlexibleListPreference(context);
duration.setKey(BluetoothDevicesProbe.FREQUENCY);
duration.setDefaultValue(Probe.DEFAULT_FREQUENCY);
duration.setEntryValues(R.array.probe_satellite_frequency_values);
duration.setEntries(R.array.probe_satellite_frequency_labels);
duration.setTitle(R.string.probe_frequency_label);
screen.addPreference(duration);
CheckBoxPreference hash = new CheckBoxPreference(context);
hash.setKey(BluetoothDevicesProbe.HASH_DATA);
hash.setDefaultValue(Probe.DEFAULT_HASH_DATA);
hash.setTitle(R.string.config_probe_bluetooth_hash_title);
hash.setSummary(R.string.config_probe_bluetooth_hash_summary);
screen.addPreference(hash);
return screen;
}
@Override
public JSONObject fetchSettings(Context context)
{
JSONObject settings = super.fetchSettings(context);
try
{
JSONArray values = new JSONArray();
values.put(true);
values.put(false);
JSONObject hash = new JSONObject();
hash.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_BOOLEAN);
hash.put(Probe.PROBE_VALUES, values);
settings.put(Probe.HASH_DATA, hash);
JSONObject frequency = new JSONObject();
frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG);
values = new JSONArray();
String[] options = context.getResources().getStringArray(R.array.probe_satellite_frequency_values);
for (String option : options)
{
values.put(Long.parseLong(option));
}
frequency.put(Probe.PROBE_VALUES, values);
settings.put(Probe.PROBE_FREQUENCY, frequency);
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
return settings;
}
public String assetPath(Context context)
{
return "visible-bluetooth-probe.html";
}
}